__getattribute__ vs __getattr__

在学习Python的时候,我们经常看到__getattribute____getattr__这两个长得很像的,那他们之间有什么联系和区别呢?

实例属性的获取和拦截

当访问某个实例属性时, __getattribute__会被无条件调用,如未实现自己的__getattr__方法,会抛出AttributeError提示找不到这个属性,如果自定义了自己__getattr__方法的话,方法会在这种找不到属性的情况下被调用。所以在找不到属性的情况下通过实现自定义的__getattr__方法来实现一些功能是一个不错的方式,因为它不会像__getattribute__方法每次都会调用可能会影响一些正常情况下的属性访问:

1
2
3
4
5
6
class Test(object):
def __init__(self, param):
self.param = param

def __getattr__(self, item):
return 'default'

注意,这里的属性指的是实例属性(instance object) 而非类属性!!,其实对类来说也有__getattribute__方法,但是只有实例属性才有__getattribute____getattr__的区别

函数的详细信息

函数的签名:

1
2
__getattr__: __getattr__(self, name)
__getattribute__:__getattribute__(self, name)

两个方法接受的参数都一样,那么他们的区别呢:

当访问一个不存在的实例属性的时候就会抛出 AttributeError异常,这个异常是由__getattribute__(self, name)抛出的,这是因为__getattribute__()会被无条件调用。
当属性不在实例的__dict__ 中并且不在基类及祖先类的__dict__中而触发AttributeError时,__getattr__()才会被调用

需要注意的问题

避免无穷递归的问题

下面代码中会产生无穷递归的问题

1
2
3
4
5
6
class A
def __getattribute__(self, attr):
try:
return self.__dict__[attr]
except KeyError:
return 'default'

这是因为属性访问调用的是覆盖了默认的__getattribute__()的方法,而在该方法中的self.__dict__[attr]又会递归的调用这个方法,于是产生了无穷递归。正确的做法是调用
super(obj, self).__getattribute__(self, attr),所以上面的代码可以改为super(A, self).__getattribute__(self,attr)

访问未定义的属性

如果在__getattr__()方法中不抛出AttributeError或者显式返回一个值,则会返回None(其实在普通函数中也一样返回None),此时可能会影响到程序的实际运行,我们来看一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class A(object):
def __init__(self, name):
self.name = name
self.x = 20

def __getattr__(self, name):
if name == 'y':
return self.x **2
elif name == 'x':
return self.x**3

def __getattribute__(self, name):
try:
return super(A,self).__getattribute__(attr)
except KeyError:
return 'default'

a = A('attribute')
print a.name
print a.z
if hasattr(a, 't'):
c = a.t
print (c)
else:
print ("instance has no attribute t")

在调用hasattr()时,__getattr__()返回的是None而不是抛出一个异常,所以hasattr()的返回值是True,因此这段程序将输出None而不是警告信息。

Author: lisupy
Link: http://lisupy.github.io/2017/03/11/getattribute vs getattr/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
支付宝打赏
微信打赏